package visitor;
import syntaxtree.*;
import visitor.*;

public class BuildSymbolTableVisitor implements Visitor {

    SymbolTable symbolTable;

    public BuildSymbolTableVisitor(){
	symbolTable = new SymbolTable();
    }

    public SymbolTable getSymbolTable(){
	return symbolTable;
    }

    private Class currClass;
    private Method currMethod;
  
  
  // Visit do nó raiz: aplique a função semântica de construir a tabela de símbolos de tal programa da seguinte forma:
  // Aumente a tabela de símbolos com a Classe Main do programa e depois faça isso para cada Declaração de Classe do 
  // Programa...
  // MainClass => m;
  // ClassDeclList =>  cl;
  public void visit(Program n) {
    
    //adicionando a classe Main na tabela de símbolos, onde i1 é o nome Main e null é pq a classe Main não tem pai...
    this.symbolTable.addClass(n.m.i1.s,null);   
    
    //adicione todos os binds de Main na tabela de símbolos de forma recursiva...
    n.m.accept(this);

    //Para cada declaração de classe simples ou decl de herança , após a main, adicione-a na tabela de símbolos e 
    //adicione seus binds na tabela de símbolos de forma recursiva...
    for ( int i = 0; i < n.cl.size(); i++ ) {
        
       if( n.cl.elementAt(i) instanceof ClassDeclSimple ){  
         ClassDeclSimple classeSimples = (ClassDeclSimple) n.cl.elementAt(i);
       
         //adiciona a classe simples na tabela de símbolos...
         this.symbolTable.addClass(classeSimples.i.s,null);
        
         //adicione seus binds na tabela de símbolos de forma recursiva...
         classeSimples.accept(this);
       }
       else if ( n.cl.elementAt(i) instanceof ClassDeclExtends ) 
       {

         ClassDeclExtends classeHerdada = (ClassDeclExtends) n.cl.elementAt(i);
       
         //adiciona a classe herdada na tabela de símbolos...
         this.symbolTable.addClass(classeHerdada.i.s,classeHerdada.j.s);
        
         //adicione seus binds na tabela de símbolos de forma recursiva...
         classeHerdada.accept(this);
 	   
	          
       }
    }

    

    
  
  }
  
  // Identifier i1,i2;
  // Onde i1 é o nome da classe e i2 é o nome da variável do Tipo String[] no método main...
  // Statement s é o corpo do método...;
  public void visit(MainClass n) {
    //Aumentar a tabela de símbolos com cada elemento da classe principal...
    //Nem i1,nem i2 e nem s possui tem o que adicionar na tabela.... 
    //A MainClass só tem um único método , o main, e não pode ter propriedades....
    //Dessa forma não é necessário fazer nada...
    n.i1.accept(this); 
    n.i2.accept(this);
    n.s.accept(this);
    
 
  }
  
  // Identifier i;
  // VarDeclList vl;
  // MethodDeclList ml;
  public void visit(ClassDeclSimple n) {
    
    n.i.accept(this); //Apenas o nome da classe simples...
    
    //Vamos localizar essa classe simples na symbolTable e adicionar
    //suas propriedades(variáveis) e métodos....
    
    Class classeNaSymbolTable = symbolTable.getClass(n.i.s);//String que representa o nome da classe...
    
    //Para cada declaração de variável da classe, coloque o par(tipo,i) dentro  dentro da classe que esta dentro da 
   //symbolTable... 
    for ( int i = 0; i < n.vl.size(); i++ ) { //Lista de declaração de variáveis...da classe
        
       //i-esima declaracao de variavel:
       VarDecl declaVar = n.vl.elementAt(i);
        
       classeNaSymbolTable.addVar(declaVar.i.s,declaVar.t);    
      
       declaVar.accept(this);
    }
    
    //Coloque cada metodo dentro da classe que ja esta dentro de SymbolTable...
    for ( int i = 0; i < n.ml.size(); i++ ) { //Lista de Declaração de Métodos... da classe
        
        MethodDecl declaMeth = n.ml.elementAt(i);
   
        classeNaSymbolTable.addMethod(declaMeth.i.s,declaMeth.t);     
    
        declaMeth.accept(this);
    }
  }
 
  //Declaração de Classe por Herança...Mesma coisa da Declaração de classe Simples...
  // Identifier i;
  // Identifier j;
  // VarDeclList vl;
  // MethodDeclList ml;
  public void visit(ClassDeclExtends n) {
    n.i.accept(this); //Apenas o nome da classe filho
    n.j.accept(this); //Apenas o nome da classe pai

    Class classeNaSymbolTable = symbolTable.getClass(n.i.s);//String que representa o nome da classe...

    //Para cada declaração de variável da classe, coloque o par(tipo,i) dentro  dentro da classe que esta dentro da 
   //symbolTable... 
    for ( int i = 0; i < n.vl.size(); i++ ) { //Lista de declaração de variáveis...da classe
        
       //i-esima declaracao de variavel:
       VarDecl declaVar = n.vl.elementAt(i);
        
       classeNaSymbolTable.addVar(declaVar.i.s,declaVar.t);        
      
       declaVar.accept(this);
    }
    
    //Coloque cada metodo dentro da classe que ja esta dentro de SymbolTable...
    for ( int i = 0; i < n.ml.size(); i++ ) { //Lista de Declaração de Métodos... da classe
        
        MethodDecl declaMeth = n.ml.elementAt(i);
   
        classeNaSymbolTable.addMethod(declaMeth.i.s,declaMeth.t);     
    
        declaMeth.accept(this);
    }
  }
  
  //Estamos agora detro de uma VarDecl ou seja uma Declaração de Variável... nada a fazer..
  // Type t;
  // Identifier i;
  public void visit(VarDecl n) 
  {
    n.t.accept(this);
    n.i.accept(this);
  }

  // Type t;
  // Identifier i;
  // FormalList fl;
  // VarDeclList vl;
  // StatementList sl;
  // Exp e;
  // Estamos agora detro de uma VarDecl ou seja uma Declaração de Método... nada a fazer..
  public void visit(MethodDecl n) { 

    n.t.accept(this); 
    n.i.accept(this); 
    
    for ( int i = 0; i < n.fl.size(); i++ ) { //Conjunto de declaração de parametros do método...     
        n.fl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.vl.size(); i++ ) { //Conjuto  de declaração de variáveis locais do método...
        n.vl.elementAt(i).accept(this);
    }
    for ( int i = 0; i < n.sl.size(); i++ ) { //Conjunto de instruções do método...
        n.sl.elementAt(i).accept(this);
    }
    n.e.accept(this);
  }

  // Type t;
  // Identifier i;
  // Parametro de um método...
  public void visit(Formal n) {
    n.t.accept(this);
    n.i.accept(this);
  }

  //Os 3 tipos abaixo não possui elementos para aumentarmos a tabela de simbolos...não é um bind e sim apenas um tipo
  public void visit(IntArrayType n) {
  }

  public void visit(BooleanType n) {
  }

  public void visit(IntegerType n) {
  }

  // String s;
  public void visit(IdentifierType n) {
    //O único elemento que  n possui é  uma String que não possui um método accept...
  }

  // StatementList sl;
  public void visit(Block n) {
    for ( int i = 0; i < n.sl.size(); i++ ) {
        n.sl.elementAt(i).accept(this);
    }
  }

  
  // Exp e;
  // Statement s1,s2;
  public void visit(If n) {
    n.e.accept(this);
    n.s1.accept(this);
    n.s2.accept(this);
  }

  // Exp e;
  // Statement s;
  public void visit(While n) {
    n.e.accept(this);
    n.s.accept(this);
  }

  // Exp e;
  public void visit(Print n) {
    n.e.accept(this);
  }
  
  // Identifier i;
  // Exp e;
  public void visit(Assign n) {
    n.i.accept(this);
    n.e.accept(this);
  }

  // Identifier i;
  // Exp e1,e2;
  public void visit(ArrayAssign n) {
    n.i.accept(this);
    n.e1.accept(this);
    n.e2.accept(this);
  }

  // Exp e1,e2;
  public void visit(And n) {
    n.e1.accept(this);
    n.e2.accept(this);
  }

  // Exp e1,e2;
  public void visit(LessThan n) {
    n.e1.accept(this);
    n.e2.accept(this);
  }

  // Exp e1,e2;
  public void visit(Plus n) {
    n.e1.accept(this);
    n.e2.accept(this);
  }

  // Exp e1,e2;
  public void visit(Minus n) {
    n.e1.accept(this);
    n.e2.accept(this);
  }

  // Exp e1,e2;
  public void visit(Times n) {
    n.e1.accept(this);
    n.e2.accept(this);
  }

  // Exp e1,e2;
  public void visit(ArrayLookup n) {
    n.e1.accept(this);
    n.e2.accept(this);
  }

  // Exp e;
  public void visit(ArrayLength n) {
    n.e.accept(this);
  }

  // Exp e;
  // Identifier i;
  // ExpList el;
  public void visit(Call n) {
    n.e.accept(this);
    n.i.accept(this);
    for ( int i = 0; i < n.el.size(); i++ ) {
        n.el.elementAt(i).accept(this);
    }
  }

  // int i;
  public void visit(IntegerLiteral n) {
  }

  public void visit(True n) {
  }

  public void visit(False n) {
  }

  // String s;
  public void visit(IdentifierExp n) {
  }

  public void visit(This n) {
  }

  // Exp e;
  public void visit(NewArray n) {
    n.e.accept(this);
  }

  // Identifier i;
  public void visit(NewObject n) {
  }

  // Exp e;
  public void visit(Not n) {
    n.e.accept(this);
  }

  // String s;
  public void visit(Identifier n) {
  }
}

